<!DOCTYPE html><html>

<head>
<meta charset="utf-8">
<title></title>
<style>
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font:inherit;font-size:100%;vertical-align:baseline}
html{line-height:1}
ol,ul{list-style:none}
table{border-collapse:collapse;border-spacing:0;
  margin-top: 0;
  margin-bottom: 0.8em;
}
caption,th,td{text-align:left;font-weight:normal;vertical-align:middle}
q,blockquote{quotes:none}
q:before,q:after,blockquote:before,blockquote:after{content:"";content:none}
a img{border:none}
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary{display:block}
a{color:#1863a1}
a:visited{color:#751590}
a:focus{color:#0181eb}
a:hover{color:#0181eb}
a:active{color:#01579f}
aside.sidebar a{color:#222}
aside.sidebar a:focus{color:#0181eb}
aside.sidebar a:hover{color:#0181eb}
aside.sidebar a:active{color:#01579f}
a{-webkit-transition:color 0.3s;-moz-transition:color 0.3s;-o-transition:color 0.3s;transition:color 0.3s}

body,h1,h2,h3,h4,h5,h6,footer{font-family:"PT Sans","Helvetica Neue","Optima","Hiragino Sans GB",sans-serif}
body{line-height:1.5em;color:#222
	-webkit-text-size-adjust:none;  min-width: 200px;
  max-width: 760px;
  margin: 0 auto; padding: 1em;}

pre,code,tt,p code,li code{font-family:Menlo,Monaco,"Andale Mono","lucida console","Courier New",monospace}

h1{font-size:2.2em;line-height:1.2em}

h1,h2,h3,h4,h5,h6{text-rendering:optimizelegibility;margin-bottom:1em;font-weight:bold}
h2,section h1{font-size:1.5em}
h3,section h2,section section h1{font-size:1.3em}
h4,section h3,section section h2,section section section h1{font-size:1em}
h5,section h4,section section h3{font-size:.9em}
h6,section h5,section section h4,section section section h3{font-size:.8em}
.markdown-body{padding: 0px 4px;}
.markdown-body h1{position:relative;padding-top:1em;padding-bottom:0.2em;margin-bottom:1em;background:url('') bottom left repeat-x}
.markdown-body  h1 a{text-decoration:none}
.markdown-body  h1 a:hover{text-decoration:underline}

.markdown-body h2{padding-top:0.8em;padding-bottom:0.2em;background:url('') bottom left repeat-x}
.markdown-body h2:first-child,.markdown-body header+h2{padding-top:0}
.markdown-body h2:first-child,.markdown-body header+h2{background:none}



p,.markdown-body blockquote,ul,ol{margin-bottom:0.8em; margin-top:0.8em;}
ul{list-style-type:disc}
ul ul{list-style-type:circle;margin-bottom:0px}
ul ul ul{list-style-type:square;margin-bottom:0px}
ol{list-style-type:decimal}
ol ol{list-style-type:lower-alpha;margin-bottom:0px}
ol ol ol{list-style-type:lower-roman;margin-bottom:0px}
ul,ul ul,ul ol,ol,ol ul,ol ol{margin-left:1.3em}
ul ul,ul ol,ol ul,ol ol{margin-bottom:0em}
strong{font-weight:bold}
em{font-style:italic}
sup,sub{font-size:0.75em;position:relative;display:inline-block;padding:0 .2em;line-height:.8em}
sup{top:-.5em}
sub{bottom:-.5em}
a[rev='footnote']{font-size:.75em;padding:0 .3em;line-height:1}
q{font-style:italic}
q:before{content:"\201C"}
q:after{content:"\201D"}
em,dfn{font-style:italic}
strong,dfn{font-weight:bold}
del,s{text-decoration:line-through}
abbr,acronym{border-bottom:1px dotted;cursor:help}
small{font-size:.8em}
big{font-size:1.2em}

.markdown-body hr {
  height: 0;
  margin: 15px 0;
  overflow: hidden;
  background: transparent;
  border: 0;
  border-bottom: 1px solid #ddd;
}

.markdown-body hr:before {
  display: table;
  content: "";
}

.markdown-body hr:after {
  display: table;
  clear: both;
  content: "";
}


.markdown-body table {
  display: block;
  width: 100%;
  overflow: auto;
  word-break: normal;
  word-break: keep-all;
}

.markdown-body table th {
  font-weight: bold;
}

.markdown-body table th,
.markdown-body table td {
  padding: 6px 13px;
  border: 1px solid #ddd;
}

.markdown-body table tr {
  background-color: #fff;
  border-top: 1px solid #ccc;
}

.markdown-body table tr:nth-child(2n) {
  background-color: #f8f8f8;
}

.markdown-body blockquote{font-style:italic;position:relative;font-size:1.2em;line-height:1.5em;padding-left:1em;border-left:4px solid rgba(170,170,170,0.5)}
.markdown-body blockquote cite{font-style:italic}
.markdown-body blockquote cite a{color:#aaa !important;word-wrap:break-word}
.markdown-body blockquote cite:before{content:'\2014';padding-right:.3em;padding-left:.3em;color:#aaa}

.markdown-body a{white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;white-space:pre-wrap;word-wrap:break-word}

body>header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em}
.markdown-body{overflow:hidden}
.markdown-body>div,.markdown-body>article{width:100%}
aside.sidebar{float:none;padding:0 18px 1px;background-color:#f7f7f7;border-top:1px solid #e0e0e0}
.flex-content,article img,article video,article .flash-video,aside.sidebar img{max-width:100%;height:auto}
.basic-alignment.left,article img.left,article video.left,article .left.flash-video,aside.sidebar img.left{float:left;margin-right:1.5em}
.basic-alignment.right,article img.right,article video.right,article .right.flash-video,aside.sidebar img.right{float:right;margin-left:1.5em}
.basic-alignment.center,article img.center,article video.center,article .center.flash-video,aside.sidebar img.center{display:block;margin:0 auto 1.5em}
.basic-alignment.left,article img.left,article video.left,article .left.flash-video,aside.sidebar img.left,.basic-alignment.right,article img.right,article video.right,article .right.flash-video,aside.sidebar img.right{margin-bottom:.8em}
.toggle-sidebar,.no-sidebar .toggle-sidebar{display:none}


.markdown-body img,.markdown-body video,.markdown-body .flash-video{ -webkit-border-radius:0.3em;-moz-border-radius:0.3em;-ms-border-radius:0.3em;-o-border-radius:0.3em;border-radius:0.3em;-webkit-box-shadow:rgba(0,0,0,0.15) 0 1px 4px;-moz-box-shadow:rgba(0,0,0,0.15) 0 1px 4px;box-shadow:rgba(0,0,0,0.15) 0 1px 4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:#fff 0.5em solid;}

.markdown-body img,.markdown-body video{max-width: 100%;}
.markdown-body video,.markdown-body .flash-video{margin:0 auto 1.5em}
.markdown-body video{display:block;width:100%}
.markdown-body .flash-video>div{position:relative;display:block;padding-bottom:56.25%;padding-top:1px;height:0;overflow:hidden}
.markdown-body .flash-video>div iframe,.markdown-body .flash-video>div object,.markdown-body .flash-video>div embed{position:absolute;top:0;left:0;width:100%;height:100%}
.markdown-body>footer{padding-bottom:2.5em;margin-top:2em}
.markdown-body>footer p.meta{margin-bottom:.8em;font-size:.85em;clear:both;overflow:hidden}


body,pre{ background:#fdf6e3 url('') top left;}
body{ background-color: #f8f8f8;}
pre{-webkit-border-radius:0.4em;-moz-border-radius:0.4em;-ms-border-radius:0.4em;-o-border-radius:0.4em;border-radius:0.4em;border:1px solid #e7dec3;line-height:1.45em;font-size:13px;margin-bottom:2.1em;padding:.8em 1em;color:#586e75;overflow:auto}
.markdown-body code{background: none;}
h3.filename+pre{-moz-border-radius-topleft:0px;-webkit-border-top-left-radius:0px;border-top-left-radius:0px;-moz-border-radius-topright:0px;-webkit-border-top-right-radius:0px;border-top-right-radius:0px}
p code,li code{display:inline-block;white-space:no-wrap;background:#fff;font-size:.8em;line-height:1.5em;color:#555;border:1px solid #ddd;-webkit-border-radius:0.4em;-moz-border-radius:0.4em;-ms-border-radius:0.4em;-o-border-radius:0.4em;border-radius:0.4em;padding:0 .3em;margin:-1px 0}
p pre code,li pre code{font-size:1em !important;background:none;border:none}

/*

Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>

*/

.hljs {
  display: block;
  overflow-x: auto;
  padding: 0.5em;
  background: #fdf6e3;
  color: #657b83;
  -webkit-text-size-adjust: none;
}

.hljs-comment,
.diff .hljs-header,
.hljs-doctype,
.hljs-pi,
.lisp .hljs-string {
  color: #93a1a1;
}

/* Solarized Green */
.hljs-keyword,
.hljs-winutils,
.method,
.hljs-addition,
.css .hljs-tag,
.hljs-request,
.hljs-status,
.nginx .hljs-title {
  color: #859900;
}

/* Solarized Cyan */
.hljs-number,
.hljs-command,
.hljs-string,
.hljs-tag .hljs-value,
.hljs-rule .hljs-value,
.hljs-doctag,
.tex .hljs-formula,
.hljs-regexp,
.hljs-hexcolor,
.hljs-link_url {
  color: #2aa198;
}

/* Solarized Blue */
.hljs-title,
.hljs-localvars,
.hljs-chunk,
.hljs-decorator,
.hljs-built_in,
.hljs-identifier,
.vhdl .hljs-literal,
.hljs-id,
.css .hljs-function,
.hljs-name {
  color: #268bd2;
}

/* Solarized Yellow */
.hljs-attribute,
.hljs-variable,
.lisp .hljs-body,
.smalltalk .hljs-number,
.hljs-constant,
.hljs-class .hljs-title,
.hljs-parent,
.hljs-type,
.hljs-link_reference {
  color: #b58900;
}

/* Solarized Orange */
.hljs-preprocessor,
.hljs-preprocessor .hljs-keyword,
.hljs-pragma,
.hljs-shebang,
.hljs-symbol,
.hljs-symbol .hljs-string,
.diff .hljs-change,
.hljs-special,
.hljs-attr_selector,
.hljs-subst,
.hljs-cdata,
.css .hljs-pseudo,
.hljs-header {
  color: #cb4b16;
}

/* Solarized Red */
.hljs-deletion,
.hljs-important {
  color: #dc322f;
}

/* Solarized Violet */
.hljs-link_label {
  color: #6c71c4;
}

.tex .hljs-formula {
  background: #eee8d5;
}


</style>

<style> @media print{ .hljs{overflow: visible; word-wrap: break-word !important;} }</style></head><body><div class="markdown-body">
<p>iOS_base组件</p>

<ul>
<li>
<a href="#toc_0">1.概述</a>
<ul>
<li>
<a href="#toc_1">1.1 功能特性</a>
</li>
<li>
<a href="#toc_2">1.2 阅读对象</a>
</li>
<li>
<a href="#toc_3">1.3 SDK架构</a>
</li>
</ul>
</li>
<li>
<a href="#toc_4">2.开发准备</a>
<ul>
<li>
<a href="#toc_5">2.1 开发环境</a>
</li>
<li>
<a href="#toc_6">2.2 Xcode配置</a>
</li>
</ul>
</li>
<li>
<a href="#toc_7">3.快速集成</a>
<ul>
<li>
<a href="#toc_8">3.1 导入framework</a>
</li>
<li>
<a href="#toc_9">3.2 framework添加Embedded Binaries</a>
</li>
<li>
<a href="#toc_10">3.3 配置依赖系统库</a>
</li>
<li>
<a href="#toc_11">3.4 流事件监听</a>
<ul>
<li>
<a href="#toc_12">3.4.1 工具类创建</a>
</li>
<li>
<a href="#toc_13">3.4.2 场景一 流事件监听（不使用排麦组件）</a>
</li>
<li>
<a href="#toc_14">3.4.3 场景二 流事件监听（使用排麦组件）</a>
</li>
<li>
<a href="#toc_15">3.4.4 线路优化监听</a>
</li>
</ul>
</li>
<li>
<a href="#toc_16">3.5 加入直播间和直播间开始结束的接口</a>
</li>
<li>
<a href="#toc_17">3.6 推流相关调用</a>
<ul>
<li>
<a href="#toc_18">3.6.1 创建本地流</a>
</li>
<li>
<a href="#toc_19">3.6.2 开启本地流预览</a>
</li>
<li>
<a href="#toc_20">3.6.3 关闭本地流预览</a>
</li>
<li>
<a href="#toc_21">3.6.4 推流</a>
</li>
<li>
<a href="#toc_22">3.6.5 结束推流</a>
</li>
</ul>
</li>
<li>
<a href="#toc_23">3.7 拉流相关掉用</a>
<ul>
<li>
<a href="#toc_24">3.7.1 订阅流</a>
</li>
<li>
<a href="#toc_25">3.7.2 取消订阅流</a>
</li>
</ul>
</li>
<li>
<a href="#toc_26">3.8 获取城市节点列表</a>
</li>
</ul>
</li>
<li>
<a href="#toc_27">4.功能使用</a>
<ul>
<li>
<a href="#toc_28">4.1 预览</a>
</li>
<li>
<a href="#toc_29">4.2 开始直播</a>
</li>
<li>
<a href="#toc_30">4.3 结束直播</a>
</li>
<li>
<a href="#toc_31">4.4 推流/取消推流</a>
</li>
<li>
<a href="#toc_32">4.5 拉流/取消拉流</a>
</li>
<li>
<a href="#toc_33">4.6 开启视频/关闭视频</a>
</li>
<li>
<a href="#toc_34">4.7 开启音频/关闭音频</a>
</li>
<li>
<a href="#toc_35">4.8 被动监听事件</a>
<ul>
<li>
<a href="#toc_36">4.8.1 用户加入房间、退出房间通知</a>
</li>
<li>
<a href="#toc_37">4.8.2 学员举手通知(举手连麦模式)</a>
</li>
<li>
<a href="#toc_38">4.8.3 用户自定义消息发送</a>
</li>
</ul>
</li>
<li>
<a href="#toc_39">4.9 单条流音视频处理</a>
</li>
</ul>
</li>
<li>
<a href="#toc_40">5.API查询</a>
</li>
<li>
<a href="#toc_41">6.Q&amp;A</a>
<ul>
<li>
<a href="#toc_42">6.1 运行崩溃</a>
</li>
</ul>
</li>
</ul>


<h2 id="toc_0">1.概述</h2>

<p>CC基础版SDK 是一个适用于iOS平台的云课堂SDK，使用此SDK可以与CC视频云服务进行对接，在iOS端使用CC视频的云课堂功能。</p>

<h3 id="toc_1">1.1 功能特性</h3>

<table>
<thead>
<tr>
<th style="text-align: left">功能</th>
<th style="text-align: left">描述</th>
<th style="text-align: left">备注</th>
</tr>
</thead>

<tbody>
<tr>
<td style="text-align: left">推流</td>
<td style="text-align: left">支持推流到服务器</td>
<td style="text-align: left"></td>
</tr>
<tr>
<td style="text-align: left">拉流</td>
<td style="text-align: left">支持从服务器订阅流</td>
<td style="text-align: left"></td>
</tr>
<tr>
<td style="text-align: left">获取流状态</td>
<td style="text-align: left">支持获取流的状态(发报数、收报数、丢包数、延时)</td>
<td style="text-align: left"></td>
</tr>
<tr>
<td style="text-align: left">前后摄像头切换</td>
<td style="text-align: left">支持手机前后摄像头切换</td>
<td style="text-align: left"></td>
</tr>
<tr>
<td style="text-align: left">后台播放</td>
<td style="text-align: left">支持直播退到后台只播放音频</td>
<td style="text-align: left"></td>
</tr>
<tr>
<td style="text-align: left">支持https协议</td>
<td style="text-align: left">支持接口https请求</td>
<td style="text-align: left"></td>
</tr>
</tbody>
</table>

<h3 id="toc_2">1.2 阅读对象</h3>

<p>本文档为技术文档，需要阅读者：</p>

<ol>
<li>具备基本的iOS开发能力</li>
<li>准备接入CC视频的云课堂SDK相关功能</li>
</ol>

<h3 id="toc_3">1.3 SDK架构</h3>

<ol>
<li>支持的CPU架构有armv7,arm64</li>
<li>支持的最低系统版本iOS9</li>
<li>模拟器支持：ipad air及以上版本，iphone 5s及以上版本模拟器；</li>
</ol>

<h2 id="toc_4">2.开发准备</h2>

<p>开发所需相关账号请咨询CC视频客户人员提供；</p>

<h3 id="toc_5">2.1 开发环境</h3>

<ol>
<li>Xcode : iOS 开发IDE</li>
</ol>

<h3 id="toc_6">2.2 Xcode配置</h3>

<pre><code>1. Build Settings   -&gt;    Build Options  -&gt;   Enable Bitcode   -&gt;       NO
</code></pre>

<pre><code>2. Build Settings -&gt; Always Enbed Swift Standar Libraries -&gt;YES
</code></pre>

<pre><code>3. 工程加入云课堂SDK相关动态库
</code></pre>

<pre><code>4. 添加需要的系统库:VideoToolbox.framework、libstdc++.tbd、libicucore.tbd
</code></pre>

<pre><code>5. General-&gt;Embedded Binaries中添加云课堂SDK相关动态库
</code></pre>

<pre><code>6. Build Settings   -&gt;   Other Linker Flags 添加-ObjC
</code></pre>

<pre><code>7. Capabilities -&gt; Background Modes -&gt; Audio，AirPlay，And Picture in Picture
</code></pre>

<pre><code>8. Info.plist 增加Privacy - Microphone Usage Description、Privacy - Photo Library Usage Description、Privacy - Camera Usage Description
</code></pre>

<h2 id="toc_7">3.快速集成</h2>

<p>注：快速集成主要提供的是推流和拉流(核心功能)。<br/>
基本的直播流程可参考Demo的相关功能函数；</p>

<p>首先，下载最新版本的组件化基础版SDK: <a href="https://doc.bokecc.com/class/developer/ios/documents/demo.html">云课堂iOS组件化SDK下载</a></p>

<h3 id="toc_8">3.1 导入framework</h3>

<table>
<thead>
<tr>
<th style="text-align: left">名称</th>
<th style="text-align: left">描述</th>
</tr>
</thead>

<tbody>
<tr>
<td style="text-align: left">CCClassRoomBasic.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">CCBarleyLibrary.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">CCChatLibrary.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">CCDocLibrary.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">CCFuncTool.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">MQTTClient.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">SocketRocket.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">ZegoLiveRoom.framework</td>
<td style="text-align: left">云课堂业务SDK</td>
</tr>
<tr>
<td style="text-align: left">HDSSup.framework</td>
<td style="text-align: left">云课堂业务SDK &lt;可选&gt;</td>
</tr>
<tr>
<td style="text-align: left">DocUI.bundle</td>
<td style="text-align: left">云课堂资源库</td>
</tr>
</tbody>
</table>

<h3 id="toc_9">3.2 framework添加Embedded Binaries</h3>

<p>由于framework是动态库需要将<br/>
CCClassRoomBasic.framework、<br/>
CCBarleyLibrary.framework、<br/>
CCChatLibrary.framework、<br/>
CCDocLibrary.framework 、<br/>
MQTTClient.framework 、<br/>
SocketRocket.framework 、<br/>
CCFuncTool.framework、<br/>
HDSSup.framework &lt;可选&gt;、<br/>
ZegoLiveRoom.framework<br/>
添加到Embedded Binaries</p>

<h3 id="toc_10">3.3 配置依赖系统库</h3>

<p>工程需要下列系统库:libz.thd、libstdc++.thd、libicucore.thd、VideoToolBox.framework</p>

<h3 id="toc_11">3.4 流事件监听</h3>

<h4 id="toc_12">3.4.1 工具类创建</h4>

<p>在需要使用SDK的文件引入头文件</p>

<pre><code class="language-objc">import &lt;CCClassRoomBasic/CCClassRoomBasic.h&gt;
</code></pre>

<p>创建SDK实例：</p>

<pre><code class="language-objc">- (void)createBasic
{
     CCEncodeConfig *config = [[CCEncodeConfig alloc] init];
    config.reslution = CCResolution_LOW;
    
    self.streamerBasic = [CCStreamerBasic sharedStreamer];
    [self.streamerBasic addObserver:self];
}
</code></pre>

<pre><code class="language-objc">#define WS(weakSelf)  __weak __typeof(&amp;*self)weakSelf = self;
#pragma mark - 流
- (void)onServerDisconnected
{
    WS(ws);
    [self.streamerBasic leave:^(BOOL result, NSError *error, id info){
       dispatch_async(dispatch_get_main_queue(), ^{
                 //退出当前控制器
                 [ws.navigationController popViewControllerAnimated:NO];
         });
    }];
}
</code></pre>

<h4 id="toc_13">3.4.2 场景一 流事件监听（不使用排麦组件）</h4>

<p><font color=#FF0000 >这里需要实现 CCStreamerBasicDelegate 相关协议</font></p>

<p>1、主要协议函数如下：</p>

<pre><code class="language-objc">/** @brief Triggers when a stream is added. */
- (void)onStreamAdded:(CCStream*)stream;

/** @brief Triggers when a stream is removed. */
- (void)onStreamRemoved:(CCStream*)stream;

</code></pre>

<p>2、示例如下：</p>

<pre><code class="language-objc">//监听流事件--有流加入房间
- (void)onStreamAdded:(CCStream*)stream
{
    //todo ，订阅相关流
}

//监听流事件--有流离开房间
- (void)onStreamRemoved:(CCStream*)stream
{
    //todo ，取消订阅相关流
}

//监听流事件--流有异常
- (void)onStreamError:(NSError *)error forStream:(CCStream *)stream
{
    CCLog(@&quot;__%@__%@&quot;, error, stream.streamID);
}
</code></pre>

<h4 id="toc_14">3.4.3 场景二 流事件监听（使用排麦组件）</h4>

<p><font color=#FF0000 ><br/>
1、这里需要实现 CCStreamerBasicDelegate 相关协议；<br/>
2、添加流事件监听；<br/>
</font></p>

<p>1、协议函数如下：</p>

<pre><code class="language-objc">//解码完成（在该函数内部进行视频渲染）
- (void)onStreamFrameDecoded:(CCStream *)stream;
</code></pre>

<p>2、添加流事件监听</p>

<pre><code class="language-objc">//一、添加流事件监听
-(void)addObserver
{
//1、有流需要订阅监听
   [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SDKNeedsubStream:) name:CCNotiNeedSubscriStream object:nil];
//2、有流需要取消订阅监听
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(SDKNeedUnsubStream:) name:CCNotiNeedUnSubcriStream object:nil];
}

//二、移除流事件监听
-(void)removeObserver
{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:CCNotiNeedSubscriStream object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:CCNotiNeedUnSubcriStream object:nil];
}
</code></pre>

<p>3、示例如下：</p>

<pre><code class="language-objc">
#pragma mark 拉流代理
//有流需要订阅
- (void)SDKNeedsubStream:(NSNotification *)notify
{
    NSDictionary *dicInfo = notify.userInfo;
    CCStream *stream = dicInfo[@&quot;stream&quot;];
    //todo ，订阅相关流
}
//有流需要取消订阅
- (void)SDKNeedUnsubStream:(NSNotification *)notify
{
    NSDictionary *dicInfo = notify.userInfo;
    CCStream *stream = dicInfo[@&quot;stream&quot;];
    //todo ，取消订阅相关流
}

//订阅流成功后，渲染视频流（异步回调）
- (void)onStreamFrameDecoded:(CCStream *)stream
{
    //主线程更新
    dispatch_async(dispatch_get_main_queue(), ^{
        //将要展示的视频流
        CCStreamView *view = [[CCStreamView alloc] initWithStream:stream];
        //放到需要展示的地方
    });
}

</code></pre>

<h4 id="toc_15">3.4.4 线路优化监听</h4>

<p>线路优化监听需要实现 CCStreamerBasicDelegate 协议的几个函数，然后在应用层做相应的逻辑处理： 具体使用可参考demo；<br/>
1、开始线路优化</p>

<pre><code class="language-objc">/*!
 @method
 @abstract 开始线路优化（1.移除预览；2.将麦克风/摄像头状态置为初始状态；）
 */
- (void)onStartRouteOptimization;

</code></pre>

<p>2、线路优化结束</p>

<pre><code class="language-objc">/*!
 @method
 @abstract 线路优化结束
*/
- (void)onStopRouteOptimization;
</code></pre>

<p>3、线路优化异常 <br/>
   优化中遇到失败，需要退出直播间，重新进入；</p>

<pre><code class="language-objc">/*!
 @method
 @abstract 线路优化异常
 */
- (void)switchPlatformError:(NSError *)error;
</code></pre>

<p>4、重新加载预览<br/>
   优化中需要重新加载预览，否则出现黑流等异常</p>

<pre><code class="language-objc">/*!
 @method 重新加载预览
 @abstract 需要重新创建本地流，创建预览
*/
- (void)onReloadPreview;

</code></pre>

<p>5、学生端下麦<br/>
    学生端优化过程中需要下麦</p>

<pre><code class="language-objc">/*!
@method
@abstract 学生端下麦
*/
- (void)onStudentDownMai;

</code></pre>

<h3 id="toc_16">3.5 加入直播间和直播间开始结束的接口</h3>

<p>加入直播间的接口</p>

<pre><code class="language-objc">    CCEncodeConfig *config = [[CCEncodeConfig alloc] init];
    config.reslution = CCResolution_LOW;
    //具体参见demo
    NSString *authSessionID = self.info[@&quot;data&quot;][@&quot;sessionid&quot;];
    NSString *user_id = self.info[@&quot;data&quot;][@&quot;userid&quot;];
    [self.streamer joinWithAccountID:self.viewerId sessionID:authSessionID config:config areaCode:nil events:nil completion:^(BOOL result, NSError *error, id info) {
       
       
    }];
    
</code></pre>

<p>开始直播</p>

<pre><code class="language-objc"> [weakSelf.streamer startLive:^(BOOL result, NSError *error, id info) {
                        if (result)
                        {
                            CCLog(@&quot;%s__%d&quot;, __func__, __LINE__);
                        }
                        else
                        {
                            [weakSelf showError:error];
                        }
                    }];

</code></pre>

<p>结束直播</p>

<pre><code class="language-objc">- (void)stopLive
{
    __weak typeof(self) weakSelf = self;
    [self.streamer stopLive:^(BOOL result, NSError *error, id info) {
        if (result)
        {
            CCLog(@&quot;%s__%d&quot;, __func__, __LINE__);
        }
        else
        {
            [weakSelf showError:error];
        }
    }];
}
</code></pre>

<h3 id="toc_17">3.6 推流相关调用</h3>

<h4 id="toc_18">3.6.1 创建本地流</h4>

<pre><code class="language-objc">#pragma mark -- 创建本地流
/*!
 @method
 @abstract 创建本地流
 @param createVideo 流是否创建视频
 @param front 设备相机
 */
- (void)createLocalStream:(BOOL)createVideo cameraFront:(BOOL)front;

</code></pre>

<h4 id="toc_19">3.6.2 开启本地流预览</h4>

<pre><code class="language-objc">#pragma mark - 开启预览
/*!
 @method (1000)
 @abstract 开始预览
 @discussion 开启摄像头开启预览，在推流开始之前开启
 @param completion 回调
 */
- (void)startPreview:(CCComletionBlock)completion;

</code></pre>

<h4 id="toc_20">3.6.3 关闭本地流预览</h4>

<pre><code class="language-objc">#pragma mark - 停止预览
/*!
 @method
 @abstract 停止预览(login out 包含该操作)
 @return 操作结果
 */
- (BOOL)stopPreview:(CCComletionBlock)completion;

</code></pre>

<h4 id="toc_21">3.6.4 推流</h4>

<pre><code class="language-objc">- (void)publish
{
    __weak typeof(self) weakSelf = self;
    [self.streamer publish:^(BOOL result, NSError *error, id info) {
        if (result)
        {
            CCLog(@&quot;%s__%d&quot;, __func__, __LINE__);
        }
        else
        {
            [weakSelf showError:error];
        }
    }];
}
</code></pre>

<h4 id="toc_22">3.6.5 结束推流</h4>

<pre><code class="language-objc">- (void)unpublish
{
    __weak typeof(self) weakSelf = self;
    [self.streamer unPublish:^(BOOL result, NSError *error, id info) {
        if (result)
        {
            CCLog(@&quot;%s__%d&quot;, __func__, __LINE__);
        }
        else
        {
            [weakSelf showError:error];
        }
    }];
}

</code></pre>

<h3 id="toc_23">3.7 拉流相关掉用</h3>

<h4 id="toc_24">3.7.1 订阅流</h4>

<pre><code class="language-objc">//1、订阅流
- (void)autoSub:(CCStream *)stream
{
    __weak typeof(self) weakSelf = self;
     [self.streamer subcribeWithStream:stream completion:^(BOOL result, NSError *error, id info) {
            if (result)
            {
                NSLog(@&quot;sub success&quot;);
            }
            else
            {
                NSLog(@&quot;sub fail&quot;);            
            }
        }];
}
//2、订阅流成功后，渲染视频流
//解码完成,加载视图
- (void)onStreamFrameDecoded:(CCStream *)stream
{
    //主线程更新
    dispatch_async(dispatch_get_main_queue(), ^{
        //将要展示的视频流
        CCStreamView *view = [[CCStreamView alloc] initWithStream:stream];
        //放到需要展示的地方
    });
}

</code></pre>

<h4 id="toc_25">3.7.2 取消订阅流</h4>

<pre><code class="language-objc">- (void)autoUnSub:(CCStream *)stream
{
    __weak typeof(self) weakSelf = self;
    [self.streamer unsubscribeWithStream:stream completion:^(BOOL result, NSError *error, id info) {
        if (result)
        {
            [weakSelf.streamView removeStreamView:info];
        }
        else
        {
            [weakSelf showError:error];
        }
    }];
}

</code></pre>

<h3 id="toc_26">3.8 获取城市节点列表</h3>

<pre><code class="language-objc">/*!
 @method
 @abstract 获取节点列表
 @param accountId 用户账号ID
 @param completion 回调
 @return 操作结果
 */
- (BOOL)getRoomServerWithAccountID:(NSString *)accountId completion:(CCComletionBlock)completion;
</code></pre>

<h2 id="toc_27">4.功能使用</h2>

<h3 id="toc_28">4.1 预览</h3>

<p>预览是将初始化相机的流渲染出来：</p>

<pre><code class="language-objc">- (void)startPreview:(CCComletionBlock)completion;
</code></pre>

<h3 id="toc_29">4.2 开始直播</h3>

<p>点击开始直播，成功以后，进行推流和拉流操作：<br/>
* 开始直播方法</p>

<pre><code class="language-objc">- (BOOL)startLive:(CCComletionBlock)completion;
</code></pre>

<h3 id="toc_30">4.3 结束直播</h3>

<p>结束当前直播：<br/>
* 结束直播方法</p>

<pre><code class="language-objc">- (BOOL)stopLive:(CCComletionBlock)completion;
</code></pre>

<h3 id="toc_31">4.4 推流/取消推流</h3>

<p>推本地相机的流到服务器：<br/>
* 推流的方法</p>

<pre><code class="language-objc">- (BOOL)publish:(CCComletionBlock)completion;
</code></pre>

<p>取消推相机本地流到服务器：<br/>
* 取消推流的方法</p>

<pre><code class="language-objc">- (BOOL)unPublish:(CCComletionBlock)completion;
</code></pre>

<h3 id="toc_32">4.5 拉流/取消拉流</h3>

<p>从服务端拉流：<br/>
* 拉流方法</p>

<pre><code class="language-objc">- (BOOL)subcribeWithStream:(CCStream *)stream completion:(CCComletionBlock)completion;
</code></pre>

<ul>
<li>渲染远程流
<code>objc
//解码完成
- (void)onStreamFrameDecoded:(CCStream *)stream;
</code></li>
</ul>

<p>取消从服务端拉流：<br/>
* 取消拉流方法</p>

<pre><code class="language-objc">- (BOOL)unsubscribeWithStream:(CCStream *)stream completion:(CCComletionBlock)completion;
</code></pre>

<h3 id="toc_33">4.6 开启视频/关闭视频</h3>

<pre><code class="language-objc">/*!
 @method
 @abstract 设置视频状态(开始直播之后生效)
 @param opened 视频状态
 @param userID 学生ID(为空表示操作自己的视频)
 
 @return 操作结果
 */
- (BOOL)setVideoOpened:(BOOL)opened userID:(NSString *)userID;
</code></pre>

<h3 id="toc_34">4.7 开启音频/关闭音频</h3>

<pre><code class="language-objc">/*!
 @method
 @abstract 设置音频状态(开始直播之后才生效)
 @param opened 音频状态
 @param userID 学生ID(为空表示操作自己的音频)
 
 @return 操作结果
 */
- (BOOL)setAudioOpened:(BOOL)opened userID:(NSString *)userID;
</code></pre>

<h3 id="toc_35">4.8 被动监听事件</h3>

<p>事件监听，建议在初始化sdk后做监听</p>

<h4 id="toc_36">4.8.1 用户加入房间、退出房间通知</h4>

<p>添加监听事件，具体参考集成示例工程；</p>

<pre><code class="language-objc">- (void)receiveSocketEvent:(NSNotification *)noti
{
    CCSocketEvent event = (CCSocketEvent)[noti.userInfo[@&quot;event&quot;] integerValue];
    id value = noti.userInfo[@&quot;value&quot;];
    
    if(event == CCSocketEvent_UserJoin)
    {
        NSString *uname = value[@&quot;name&quot;];
        NSString *msg = [NSString stringWithFormat:@&quot;&lt;%@&gt; 加入房间!&quot;,uname];
        [self showMessage:msg];
    }
    else if(event == CCSocketEvent_UserExit)
    {
        NSString *uname = value[@&quot;name&quot;];
        NSString *msg = [NSString stringWithFormat:@&quot;&lt;%@&gt; 离开房间!&quot;,uname];
        [self showMessage:msg];
    }
}
</code></pre>

<h4 id="toc_37">4.8.2 学员举手通知(举手连麦模式)</h4>

<p>添加监听事件，具体参考集成示例工程；</p>

<pre><code class="language-objc">- (void)receiveSocketEvent:(NSNotification *)noti
{
    CCSocketEvent event = (CCSocketEvent)[noti.userInfo[@&quot;event&quot;] integerValue];
    id value = noti.userInfo[@&quot;value&quot;];
    NSLog(@&quot;%s__%@__%@&quot;, __func__, noti.name, @(event));
    
    if(event == CCSocketEvent_UserHandUp)
    {
        NSString *name = value[@&quot;name&quot;];
        NSString *str = [NSString stringWithFormat:@&quot;&lt;%@&gt; 举手了！&quot;,name];
        [self showMessage:str];
    }
}
</code></pre>

<h4 id="toc_38">4.8.3 用户自定义消息发送</h4>

<p>添加监听事件，具体参考集成示例工程；</p>

<pre><code class="language-objc">- (void)receiveSocketEvent:(NSNotification *)noti
{
    CCSocketEvent event = (CCSocketEvent)[noti.userInfo[@&quot;event&quot;] integerValue];
    id value = noti.userInfo[@&quot;value&quot;];
    NSLog(@&quot;%s__%@__%@&quot;, __func__, noti.name, @(event));
    
    if(event == CCSocketEvent_PublishMessage)
    {
        NSString *val = value[@&quot;value&quot;];
        NSString *smessage = [NSString stringWithFormat:@&quot;收到消息:%@&quot;,val];
    }
}
</code></pre>

<h3 id="toc_39">4.9 单条流音视频处理</h3>

<p>//pragma mark -- 修改远程流接收状态</p>

<pre><code class="language-objc">#pragma mark - 设置流视频的状态
/*!
 @method
 @abstract 设置流视频的状态
 @param stream  流
 @param video   视频流状态(开启/关闭)
 @param completion 成功闭包
  @return 操作结果
 */
- (BOOL)changeStream:(CCStream *)stream videoState:(BOOL)video completion:(CCComletionBlock)completion;
- 
#pragma mark - 设置流音频的状态
/*!
 @method
 @abstract 设置流音频的状态
 @param stream  流
 @param audio   音频流状态(开启/关闭)
 @param completion 回调闭包
 @return 操作结果
 */
- (BOOL)changeStream:(CCStream *)stream audioState:(BOOL)audio completion:(CCComletionBlock)completion;
</code></pre>

<h2 id="toc_40">5.API查询</h2>

<p>Document目录打开index.html文件</p>

<h2 id="toc_41">6.Q&amp;A</h2>

<h3 id="toc_42">6.1 运行崩溃</h3>

<pre><code class="language-objc">dyld: Library not loaded: @rpath/CCClassRoomBasic.framework/CCClassRoomBasic
  Referenced from: /var/containers/Bundle/Application/E8CDE526-6F19-415B-9BA4-2380AB0A1FDE/CCClassRoom.app/CCClassRoom
  Reason: image not found
</code></pre>

<p>解决办法参考3.2</p>

<br><br><br><br>

</div></body>

</html>
